//////////////////////////////////////////////////////////////////////////
/// include standard headers
//////////////////////////////////////////////////////////////////////////
#include <stdio.h>

//////////////////////////////////////////////////////////////////////////
/// include cuda headers
//////////////////////////////////////////////////////////////////////////
#include "../cuCommon/cuTypes.h"

#include "../gui/C_Mouse.h"

#include "../rtCommon/C_Camera.h"

#include "../interface/I_SceneGraph.h"
#include "../interface/I_api_Device.h"

#include "../interface/I_Buffer2D.h"
#include "../interface/T_Buffer2D_GL.h"

#include "../interface/T_gl_SceneGraph.h"
#include "../interface/C_gl_Device.h"
#include "../interface/C_gl_Common.h"
#include "../rtCommon/F_trace.h"

//////////////////////////////////////////////////////////////////////////
/// include api headers
//////////////////////////////////////////////////////////////////////////
#include "../glew/glew.h"
#include "../glut/glut.h"

// Idle() is called every time in the message processing loop. It is the main callback function 
void Idle(void);
// Display() is called whenever window content is invalidated
void Display(void);
// Keyboard() is called whenever any of the character keys are pressed
void Keyboard(uint8 key, int32 mousex, int32 mousey);
// KeyboardUp() is called whenever any of the pressed character keys is released
void KeyboardUp(uint8 key, int32 mousex, int32 mousey);
// Special() is called whenever any of the special (F1-F12, etc) keys are pressed
void Special(int32 key, int32 x, int32 y);
// SpecialUp() is called whenever any of the pressed special (F1-F12, etc) keys is released
void SpecialUp(int32 key, int32 x, int32 y);
// Reshape() is called when window size is changed
void Reshape(int32 w, int32 h);
// Mouse() is called whenever user clicks any button
void Mouse(int32 button, int32 state, int32 mousex, int32 mousey);
// Motion() is called whenever user holds any of the buttons while moving mouse
void Motion(int32 mousex, int32 mousey);
// PassiveMotion() is called whenever mouse moves and no buttons are pressed
void PassiveMotion(int32 mousex, int32 mousey);
// Entry() is called when mouse cursor enters / leaves current window
void Entry(int32 state);

namespace N_Gfx
{
	int2 Resolution = {256, 256};
	int2 Position = {0, 0};
	int  Flags = GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE;
	int2 AA = {1, 1};
	int Id = 0;
	bool Fullscreen = false;
}

C_Mouse g_Mouse;
C_Camera g_Camera;
I_SceneGraph * g_pSceneGraph = NULL;
I_api_Device * g_pDevice = NULL;
I_api_Common * g_pApiCommon = NULL;
I_api_Framebuffer	*	g_pDeferredFramebuffer = NULL;
I_api_Framebuffer	*	g_pGlutFramebuffer = NULL;

I_Buffer2D * g_pRayO = NULL;
I_Buffer2D * g_pRayD = NULL;
I_Buffer2D * g_pDepth = NULL;
I_Buffer2D * g_pRgba8 = NULL;

S_api_Texture2D_Desc										g_TextureDesc;
S_api_Subresource_Data									g_TextureData;
S_api_ShaderResourceView_Desc						g_srvDesc;
S_api_DepthStencilView_Desc							g_dsvDesc;
S_api_RenderTargetView_Desc							g_rtvDesc;
S_api_DepthStencilState									g_dsState;
S_api_Rasterizer_Desc										g_rsState;

I_api_Texture_2D				*	g_ppTexture2D[4] = { NULL, NULL, NULL, NULL };
I_api_RenderTargetView	*	g_ppRenderTarget[3] = { NULL, NULL, NULL };
I_api_ShaderResourceView*	g_ppShaderResource[3] = { NULL, NULL, NULL };
I_api_DepthStencilView	*	g_pDepthStencil = NULL;
I_api_DepthStencilState	*	g_pDepthStencilState = NULL;
I_api_RasterizerState		*	g_pRasterizerState = NULL;

// color / primitive id and t / world position / depth
E_api_Format														g_ViewFormat[4]			= { E_api_Format_r8g8b8a8_uint, E_api_Format_r32g32b32a32_float, E_api_Format_r8g8b8a8_unorm, E_api_Format_d24_unorm };
E_api_Format														g_TextureFormat[4]	= { E_api_Format_r8g8b8a8_uint, E_api_Format_r32g32b32a32_float, E_api_Format_r8g8b8a8_unorm, E_api_Format_d24_unorm };
I_api_Program * g_pDefaultProgram = NULL;

bool g_bReshape = false; 
bool g_bIdle = false;
bool g_bTraceSingleRay = false;
bool g_bRenderBVHTraceSingleRay = false;
bool g_bTraceInstanced = false;
bool g_bTraceIndexedInstanced = false;
float3 g_RayO;
float3 g_RayD;
int		 g_RayI;

int g_nMaxLevel = 0;

int main(int argc, char ** argv)
{
	glutInit( &argc, argv );   

	glutInitWindowSize     ( N_Gfx::Resolution.x, N_Gfx::Resolution.y );    
	glutInitWindowPosition ( N_Gfx::Position.x,   N_Gfx::Position.y   );
	glutInitDisplayMode    ( N_Gfx::Flags                            );

	N_Gfx::Id = glutCreateWindow("ray tracing test window");

	if (glewInit() != GLEW_OK) fprintf(stderr, "Error: initializig glew\n"); 
	else fprintf(stdout, "Status: Using GLEW %s \n", glewGetString(GLEW_VERSION)); 

	glutDisplayFunc    ( Display  );
	glutIdleFunc       ( Idle     );
	glutKeyboardFunc   ( Keyboard );
	glutSpecialFunc    ( Special  );
	glutReshapeFunc    ( Reshape  );
	glutMouseFunc      ( Mouse    );
	glutMotionFunc     ( Motion   );

	//// create a texture
	cudaGLSetGLDevice(0);

	u32 tex_name;
	cudaGraphicsResource * cuda_tex_source_resource = NULL;

	glGenTextures(1, &tex_name);
	glBindTexture(GL_TEXTURE_2D, tex_name);

	// set basic parameters
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	// buffer data
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI_EXT, 512, 512, 
		0, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_BYTE, NULL);

	// register this texture with CUDA
	cudaGraphicsGLRegisterImage(&cuda_tex_source_resource, tex_name, GL_TEXTURE_2D, cudaGraphicsMapFlagsReadOnly);

	if (N_Gfx::Fullscreen)
		glutFullScreen();  

	g_pDevice = new C_gl_Device((void *)&N_Gfx::Id);
	g_pApiCommon = new C_gl_Common();

	g_pSceneGraph = new C_gl_SceneGraph(g_pDevice, g_pApiCommon);

	//g_pSceneGraph->Deserialize("..//Media//scenegraph//test_bvh.xml");
	//g_pSceneGraph->Deserialize("..//Media//scenegraph//test_actor_tree.xml");
	g_pSceneGraph->Deserialize("..//Media//scenegraph//scenegraph.xml");

	g_pSceneGraph->Build(e_SpatialTree_Bvh);

	g_pDevice->CreateProgram(NULL, NULL, &g_pDefaultProgram, NULL);

	g_pRayO = new T_Buffer2D_GL<float4>();
	g_pRayD = new T_Buffer2D_GL<float4>();
	g_pDepth = new T_Buffer2D_GL<float>();
	g_pRgba8 = new T_Buffer2D_GL<uchar4>();

	g_TextureDesc.ArraySize = 1;
	g_TextureDesc.Width = N_Gfx::Resolution.x;
	g_TextureDesc.Height = N_Gfx::Resolution.y;
	g_TextureDesc.BindFlags = E_api_Bind_Render_Target | E_api_Bind_Shader_Resource;
	g_TextureDesc.CPUAccessFlags = 0;
	g_TextureDesc.MipLevels = 1;
	g_TextureDesc.SampleDesc.Count = 1;
	g_TextureDesc.SampleDesc.Quality = 0;
	g_TextureDesc.Usage = E_api_Usage_Dynamic;
	g_TextureDesc.Format = E_api_Format_r8g8b8a8_unorm;

	g_TextureData.Format = E_api_Format_r8g8b8a8_unorm;
	g_TextureData.pSysMem = NULL;
	g_TextureData.SysMemPitch = g_TextureDesc.Width * sizeof(uchar4);
	g_TextureData.SysMemSlicePitch = 0;

	g_rtvDesc.Format = E_api_Format_r8g8b8a8_unorm;
	g_rtvDesc.Texture2D.MipSlice = 0;
	g_rtvDesc.ViewDimension = E_api_RTV_Dimension_Texture2d;

	g_srvDesc.Format = E_api_Format_r8g8b8a8_unorm;
	g_srvDesc.ViewDimension = E_api_SRV_Dimension_Texture2d;
	g_srvDesc.Texture2D.MipLevels = g_TextureDesc.MipLevels;
	g_srvDesc.Texture2D.MostDetailedMip = 0;

	g_dsvDesc.Format = E_api_Format_d24_unorm_s8_uint;
	g_dsvDesc.ViewDimension = E_api_DSV_Dimension_Texture2d;
	g_dsvDesc.Texture2D.MipSlice = 0;

	g_pSceneGraph->Compress();

	g_Camera.Eye(-75, 0, 0);
	g_Camera.Dir(1, 0, 0);

	g_pDevice->CreateFramebuffer(0, NULL, NULL, &g_pGlutFramebuffer);	

	g_pDevice->CreateDepthStencilState(&g_dsState, &g_pDepthStencilState);
	g_pDevice->OMSetDepthStencilState(g_pDepthStencilState, 0);

	g_pDevice->CreateRasterizerState(&g_rsState, &g_pRasterizerState);
	g_pDevice->RSSetState(g_pRasterizerState);

	Reshape(N_Gfx::Resolution.x, N_Gfx::Resolution.y);

	glutMainLoop();

	return 0;
}

void Display(void)
{
	float clearcolor[4] = {0.0, 0.0, 0.0, 1.0};

	if (!g_bTraceInstanced) {
		g_pDevice->OMSetFramebuffer(g_pDeferredFramebuffer);
		g_pDevice->ClearFramebuffer(g_pDeferredFramebuffer, E_api_Clear_Color | E_api_Clear_Depth, clearcolor, 1.0f, 0);

		g_pSceneGraph->Render(&g_Camera);

		g_pDevice->OMSetFramebuffer(g_pGlutFramebuffer);
		g_pDevice->ClearFramebuffer(g_pGlutFramebuffer, E_api_Clear_Color | E_api_Clear_Depth, clearcolor, 1.0f, 0);

		g_pDevice->SetProgram(g_pDefaultProgram);
		matrix4x4 identity;
		Matrix4x4SetIdentity(&identity);
		glRasterPos2i(-1, -1);
		g_pDefaultProgram->GetVariableByIndex(E_api_Program_Variable_ModelView)->AsMatrix()->SetMatrix((float *)&identity);
		g_pDefaultProgram->GetVariableByIndex(E_api_Program_Variable_Projection)->AsMatrix()->SetMatrixTranspose((float *)&identity);

		glBindTexture(GL_TEXTURE_2D, *((int *)g_ppTexture2D[0]->Get()));
		glBegin(GL_QUADS);
		glTexCoord2f(0,0);
		glVertex2f(-0.9,-0.9);
		glTexCoord2f(0,1);
		glVertex2f(-0.9,0.9);
		glTexCoord2f(1,1);
		glVertex2f( 0.9, 0.9);
		glTexCoord2f(1,0);
		glVertex2f( 0.9,-0.9);
		glEnd();	

		g_pSceneGraph->Shade(g_ppShaderResource[0], g_ppShaderResource[1], g_ppShaderResource[2]);
	}

	if (g_bReshape && g_bIdle) 
	{
		float fmax = CU_MAX_F32;
		uchar4 black = uc4();

		g_pDepth->Memset(&fmax);
		g_pRgba8->Memset(&black);

		if (g_bTraceSingleRay)
			g_pSceneGraph->TraceIndexedInstanced(g_RayO, g_RayD, g_RayI, g_pDepth->pBuffer(), NULL, NULL, g_pRgba8->pBuffer());

		if (g_bRenderBVHTraceSingleRay)
			g_pSceneGraph->RenderBVHTreeRayTrace(&g_Camera, g_RayO, g_RayD, 10, true);

		if (g_bTraceInstanced){
			//g_pSceneGraph->TraceIndexedInstanced(g_pRayO->pBuffer(), g_pRayD->pBuffer(), g_pDepth->pBuffer(), NULL, NULL, g_pRgba8->pBuffer());
			g_pSceneGraph->TraceInstanced(g_pRayO->pBuffer(), g_pRayD->pBuffer(), g_pDepth->pBuffer(), NULL, NULL, g_pRgba8->pBuffer());
		}

		if (g_bTraceIndexedInstanced){
			g_pSceneGraph->TraceIndexedInstanced(g_pRayO->pBuffer(), g_pRayD->pBuffer(), g_pDepth->pBuffer(), NULL, NULL, g_pRgba8->pBuffer());
			//g_pSceneGraph->TraceInstanced(g_pRayO->pBuffer(), g_pRayD->pBuffer(), g_pDepth->pBuffer(), NULL, NULL, g_pRgba8->pBuffer());
		}

		if (g_bTraceInstanced || g_bTraceIndexedInstanced) {
			g_pDevice->OMSetFramebuffer(g_pGlutFramebuffer);
			g_pDevice->ClearFramebuffer(g_pGlutFramebuffer, E_api_Clear_Color | E_api_Clear_Depth, clearcolor, 1.0f, 0);

			g_pDevice->SetProgram(g_pDefaultProgram);
			matrix4x4 identity;
			Matrix4x4SetIdentity(&identity);
			glRasterPos2i(-1, -1);
			glBindTexture(GL_TEXTURE_2D, 0);
			g_pDefaultProgram->GetVariableByIndex(E_api_Program_Variable_ModelView)->AsMatrix()->SetMatrix((float *)&identity);
			g_pDefaultProgram->GetVariableByIndex(E_api_Program_Variable_Projection)->AsMatrix()->SetMatrixTranspose((float *)&identity);

			glDrawPixels(N_Gfx::Resolution.x, N_Gfx::Resolution.y, GL_RGBA, GL_UNSIGNED_BYTE, g_pRgba8->pBuffer()->hPtr());
		}
	}

	glCheckError(__LINE__, "application_test");

	glutSwapBuffers();
}

void Idle()
{
	Display();

	if (g_bReshape){
		mpGenerateEyeRays(g_Camera, N_Gfx::Resolution, N_Gfx::AA, 
											e_Eye_Ray_Perspective, 
											(float4 * ) g_pRayO->hPtr(), 
											(float4 * ) g_pRayD->hPtr());
		g_bIdle = true;
	}
}

void Reshape(int32 w, int32 h)
{
	if (w % 32 != 0)
		glutReshapeWindow((w / 32 + 1) * 32, h); // force window to be 32 * X in width

	g_bReshape = true;

	glViewport(0, 0, w, h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	N_Gfx::Resolution.x = w;
	N_Gfx::Resolution.y = h;

	g_TextureDesc.Width = w;
	g_TextureDesc.Height = h;

	for (int iTex = 0; iTex < 4; iTex++) 
	{
		if (g_ppTexture2D[iTex] != NULL) { delete g_ppTexture2D[iTex]; g_ppTexture2D[iTex] = NULL; }
		
		g_TextureDesc.Format = g_TextureFormat[iTex];
		g_TextureData.Format = g_TextureFormat[iTex];

		if (iTex < 3) 
		{
			g_rtvDesc.Format = g_ViewFormat[iTex];
			g_srvDesc.Format = g_ViewFormat[iTex];
			if (g_ppRenderTarget[iTex] != NULL) { delete g_ppRenderTarget[iTex]; g_ppRenderTarget[iTex] = NULL; }
			g_pDevice->CreateTexture2D(&g_TextureDesc, &g_TextureData, &g_ppTexture2D[iTex]);
			g_pDevice->CreateRenderTargetView(g_ppTexture2D[iTex], &g_rtvDesc, &g_ppRenderTarget[iTex]);
			g_pDevice->CreateShaderResourceView(g_ppTexture2D[iTex], &g_srvDesc, &g_ppShaderResource[iTex]);
		}
		else
		{
			g_dsvDesc.Format = g_ViewFormat[iTex];
			if (g_pDepthStencil) { delete g_pDepthStencil; g_pDepthStencil = NULL; }
			g_pDevice->CreateTexture2D(&g_TextureDesc, &g_TextureData, &g_ppTexture2D[iTex]);
			g_pDevice->CreateDepthStencilView(g_ppTexture2D[iTex], &g_dsvDesc, &g_pDepthStencil);
		}
	}

	g_pDevice->CreateFramebuffer(3, g_ppRenderTarget, g_pDepthStencil, &g_pDeferredFramebuffer);

	float asp = w / (float) h;

	g_Camera.Asp(asp);

	g_pRayO->Create(w, h, 0, g_pDevice);
	g_pRayD->Create(w, h, 0, g_pDevice);
	g_pDepth->Create(w, h, 0, g_pDevice);
	g_pRgba8->Create(w, h, 0, g_pDevice);
}

void Keyboard(uint8 key, int32 mousex, int32 mousey)
{
	switch (key)
	{
#define GLUT_KEY_ESC 27
	case GLUT_KEY_ESC: 
		glutDestroyWindow(N_Gfx::Id);
		// any cpu resource clean-up should go here
		exit(0);
		break;
	case '[':
		g_nMaxLevel++;
		break;
	case ']':
		g_nMaxLevel--; g_nMaxLevel = max(g_nMaxLevel, 0);
		break;

	case 's': g_bTraceSingleRay = !g_bTraceSingleRay;
		break;

	case 'r': g_bTraceInstanced = !g_bTraceInstanced;
						if (g_bTraceInstanced) g_bTraceIndexedInstanced = false;
		break;

	case 'i': g_bTraceIndexedInstanced = !g_bTraceIndexedInstanced;
						if (g_bTraceIndexedInstanced) g_bTraceInstanced = false;
		break;

	case '1': g_bRenderBVHTraceSingleRay = !g_bRenderBVHTraceSingleRay;
		break;

	default:
		break;
	}
}

void Special(int32 key, int32 mousex, int32 mousey)
{
	switch (key)
	{
	case GLUT_KEY_LEFT:
		g_Camera.Update((uint32) e_StrafeLeft, 0.5f);
		break;
	case GLUT_KEY_RIGHT:
		g_Camera.Update((uint32) e_StrafeRight, 0.5f);
		break;
	case GLUT_KEY_UP:
		g_Camera.Update((uint32) e_StepForward, 0.5f);
		break;
	case GLUT_KEY_DOWN:
		g_Camera.Update((uint32) e_StepBackward, 0.5f);
		break;

	default:
		break;
	}
}

void Mouse(int32 button, int32 state, int32 mousex, int32 mousey)
{
	g_Mouse.Update(mousex, mousey, button, !state);

	if (button == GLUT_RIGHT_BUTTON)
	{
		mpGenerateEyeRay(g_Camera, N_Gfx::Resolution, e_Eye_Ray_Perspective, mousex, N_Gfx::Resolution.y - mousey, g_RayO, g_RayD, g_RayI);

		printf("ray %d \n", g_RayI);
	}
}

void Motion(int32 mousex, int32 mousey)
{
	g_Mouse.Update(mousex, mousey);

	if (g_Mouse.Pressed( (int32) e_Left))
	{
		int dx = g_Mouse.currX() - g_Mouse.prevX();
		int dy = g_Mouse.currY() - g_Mouse.prevY();

		g_Camera.Update(dx * 0.001f, -dy * 0.001f);
	}
}

void KeyboardUp(uint8 key, int32 mousex, int32 mousey)
{
	//switch (key)
	//{
	//    default:
	//    break;
	//}
}

void SpecialUp(int32 key, int32 mousex, int32 mousey)
{
	//switch (key)
	//{
	//    default:
	//    break;
	//}
}
